package com.leyou.search.service.impl;

import com.leyou.common.dto.PageDTO;
import com.leyou.common.exception.LyException;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.SkuDTO;
import com.leyou.item.dto.SpecParamDTO;
import com.leyou.item.dto.SpuDTO;
import com.leyou.search.dto.SearchParamDTO;
import com.leyou.search.entity.Goods;
import com.leyou.search.repository.GoodsRepository;
import com.leyou.search.service.SearchService;
import com.leyou.starter.elastic.entity.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.nested.Nested;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Mono;
import sun.reflect.generics.scope.Scope;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

import static com.leyou.search.constants.SearchConstants.*;

@Slf4j
@Service
public class SearchServiceImp implements SearchService {

    @Resource
    private GoodsRepository goodsRepository;

    @Resource
    private ItemClient itemClient;

    @Override
    public void createIndexAndMapping() {
        // 删除已经存在的索引库
        try {
            goodsRepository.deleteIndex();
            log.info("索引库删除成功");
        } catch (Exception e) {
            log.error("删除失败，可能索引库不存在！", e);
        }
        // 然后创建一个新的
        goodsRepository.createIndex("{\n" +
                "  \"settings\": {\n" +
                "    \"analysis\": {\n" +
                "      \"analyzer\": {\n" +
                "        \"my_pinyin\": {\n" +
                "          \"tokenizer\": \"ik_smart\",\n" +
                "          \"filter\": [\n" +
                "            \"py\"\n" +
                "          ]\n" +
                "        }\n" +
                "      },\n" +
                "      \"filter\": {\n" +
                "        \"py\": {\n" +
                "\t\t  \"type\": \"pinyin\",\n" +
                "          \"keep_full_pinyin\": true,\n" +
                "          \"keep_joined_full_pinyin\": true,\n" +
                "          \"keep_original\": true,\n" +
                "          \"limit_first_letter_length\": 16,\n" +
                "          \"remove_duplicated_term\": true\n" +
                "        }\n" +
                "      }\n" +
                "    }\n" +
                "  },\n" +
                "  \"mappings\": {\n" +
                "    \"properties\": {\n" +
                "      \"id\": {\n" +
                "        \"type\": \"keyword\"\n" +
                "      },\n" +
                "      \"suggestion\": {\n" +
                "        \"type\": \"completion\",\n" +
                "        \"analyzer\": \"my_pinyin\",\n" +
                "        \"search_analyzer\": \"ik_smart\"\n" +
                "      },\n" +
                "      \"title\":{\n" +
                "        \"type\": \"text\",\n" +
                "        \"analyzer\": \"my_pinyin\",\n" +
                "        \"search_analyzer\": \"ik_smart\"\n" +
                "      },\n" +
                "      \"image\":{\n" +
                "        \"type\": \"keyword\",\n" +
                "        \"index\": false\n" +
                "      },\n" +
                "      \"updateTime\":{\n" +
                "        \"type\": \"date\"\n" +
                "      },\n" +
                "      \"specs\":{\n" +
                "        \"type\": \"nested\",\n" +
                "        \"properties\": {\n" +
                "          \"name\":{\"type\": \"keyword\" },\n" +
                "          \"value\":{\"type\": \"keyword\" }\n" +
                "        }\n" +
                "      }\n" +
                "    }\n" +
                "  }\n" +
                "}");

        log.info("索引库创建成功");
    }

    @Override
    public void loadData() {
        int page = 1, rows = 100;
        while (true) {
            log.info("开始导入第{}页数据", page);
            // 分页查询已经上架的spu
            PageDTO<SpuDTO> result = itemClient.querySpuByPage(page, rows, true, null, null, null);
            List<SpuDTO> list = result.getItems();

            // 遍历Spu集合，把SpuDTO通过buildGoods方法转为Goods
            List<Goods> goodsList = list.stream()
                    .map(this::buildGoods).collect(Collectors.toList());

            // 批量写入Elasticsearch
            goodsRepository.saveAll(goodsList);
            log.info("导入第{}页数据结束。", page);
            // 翻页
            page++;

            // 获取总页数
            Long totalPage = result.getTotalPage();
            // 判断是否还有spu没有查询
            if (page > totalPage) {
                // 没有则结束
                break;
            }
        }
    }

    @Override
    public Mono<List<String>> getSuggestion(String keyPrefix) {
        if (StringUtils.isBlank(keyPrefix)){
            throw new LyException(400,"查询关键字不能为空");
        }
        return goodsRepository.suggestBySingleField(SUGGESTION_FIELD,keyPrefix);
    }

    @Override
    public Mono<PageInfo<Goods>> searchGoods(SearchParamDTO searchParam) {
        //1.构建查询条件的工厂对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //1.1 source过滤
        sourceBuilder.fetchSource(DEFAULT_SOURCE_FIELD,new String[0]);
//        //1.2 查询条件
//        String key = searchParam.getKey();
//        if (StringUtils.isBlank(key)){
//            // 搜索条件为null，返回异常
//            throw new LyException(400, "搜索条件不能为空！");
//        }
//        sourceBuilder.query(QueryBuilders.matchQuery(DEFAULT_SEARCH_FIELD,key));

        //获取查询条件
        sourceBuilder.query(getQueryBuilder(searchParam));

        // 1.3.分页条件
        sourceBuilder.from(searchParam.getFrom());
        sourceBuilder.size(searchParam.getSize());
        // 1.4.排序条件
        if (StringUtils.isNotBlank(searchParam.getSortBy())){
            //排序字段存在，才去排序
            sourceBuilder.sort(searchParam.getSortBy(),searchParam.getDesc()? SortOrder.DESC : SortOrder.ASC);
        }
        // 1.5.高亮条件
        sourceBuilder.highlighter(new HighlightBuilder().field(DEFAULT_SEARCH_FIELD)
                                    .preTags(DEFAULT_PRE_TAG)
                                    .postTags(DEFAULT_POST_TAG));
        // 2.查询
        return goodsRepository.queryBySourceBuilderForPageHighlight(sourceBuilder);

    }

    @Override
    public Mono<Map<String, List<?>>> listFilter(SearchParamDTO searchParam) {
        //1.构建查询条件的工厂对象
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//        //1.1 查询条件
//        String key = searchParam.getKey();
//        if (StringUtils.isBlank(key)){
//            // 搜索条件为null，返回异常
//            throw new LyException(400, "搜索条件不能为空！");
//        }
//        //1.2 查询
//        sourceBuilder.query(QueryBuilders.matchQuery(DEFAULT_SEARCH_FIELD,key).operator(Operator.AND));

        //获取查询条件
        sourceBuilder.query(getQueryBuilder(searchParam));

        //1.3只查询不展示
        sourceBuilder.from(0);
        sourceBuilder.size(0);

        //2.添加聚合条件，并设置分桶的最大展示数量
        sourceBuilder.aggregation(AggregationBuilders.terms("brandAgg").field(BRAND_FIELD_NAME).size(50));
        sourceBuilder.aggregation(AggregationBuilders.terms("categoryAgg").field(CATEGORY_FIELD_NAME).size(50));

        //添加其他条件，对其他可搜索过滤条件进行聚合
        sourceBuilder.aggregation(
                AggregationBuilders.nested("specAgg",SPEC_FIELD_PATH)
                        .subAggregation(AggregationBuilders.terms("nameAgg").field(SPEC_NAME_FIELD_NAME).size(50)
                                .subAggregation(AggregationBuilders.terms("valueAgg").field(SPEC_VALUE_FIELD_NAME).size(50)))
        );


        //3.把Mono<Aggregations>===>Mono<Map<String,List<?>>>
        return goodsRepository.aggregationBySourceBuilder(sourceBuilder)
                .map(aggregations -> {

                    Map<String, List<?>> resultMap = new LinkedHashMap<>();
                    //根据聚合名称取值
                    Terms categoryAgg = aggregations.get("categoryAgg");
                    //获取到分类id的集合
                    List<Long> categoryIds  = categoryAgg
                            .getBuckets()
                            .stream()
                            .map(bucket -> ((Terms.Bucket) bucket)
                                    .getKeyAsNumber()
                                    .longValue())
                            .collect(Collectors.toList());
                    //封装分类信息集合
                    if (!CollectionUtils.isEmpty(categoryIds)){
                        resultMap.put("分类",itemClient.queryCategoryByIds(categoryIds));
                    }

                    //根据聚合名称取值
                    Terms brandAgg = aggregations.get("brandAgg");
                    //获取到品牌id的集合
                    List<Long> brandIds = brandAgg
                            .getBuckets()
                            .stream()
                            .map(bucket -> ((Terms.Bucket) bucket)
                                    .getKeyAsNumber()
                                    .longValue())
                            .collect(Collectors.toList());
                    //封装品牌信息集合
                    if (!CollectionUtils.isEmpty(brandIds)){
                        resultMap.put("品牌",itemClient.queryBrandByIds(brandIds));
                    }

                    //解析过滤参数的聚合结果
                    Nested specAgg = aggregations.get("specAgg");

                    Terms nameAgg = specAgg.getAggregations().get("nameAgg");

                    nameAgg.getBuckets().forEach(bucket->{

                        //获取到过滤参数的名称
                        String key = ((Terms.Bucket) bucket).getKeyAsString();

                        Terms valueAgg = ((Terms.Bucket) bucket).getAggregations().get("valueAgg");

                        //valueList就是每个规格属性对应的值的集合
                        List<String> valueList = valueAgg.getBuckets().stream().map(bkt -> bkt.getKeyAsString()).collect(Collectors.toList());
                        System.out.println("key = " + key);
                        resultMap.put(key, valueList);
                    });


                    return resultMap;
                });

    }

    @Override
    public void saveGoodsById(Long spuId) {
        // 需要spu
        SpuDTO spu = itemClient.queryGoodsById(spuId);
        // 构建goods
        Goods goods = buildGoods(spu);
        // 写入索引库
        goodsRepository.save(goods);
    }

    @Override
    public void deleteGoodsById(Long spuId) {
        goodsRepository.deleteById(spuId);
    }

    //构建查询条件的方法
    private QueryBuilder getQueryBuilder(SearchParamDTO searchRequest) {
        String key = searchRequest.getKey();
        if (StringUtils.isBlank(key)){
            throw new LyException(400, "查询条件不能为空");
        }

        //创建boolean查询条件构建对象
        BoolQueryBuilder queryBuilder = new BoolQueryBuilder();
        //添加查询条件
        queryBuilder.must(QueryBuilders.matchQuery(DEFAULT_SEARCH_FIELD,key).operator(Operator.AND));

        //获取所有过滤条件
        Map<String, String> filters = searchRequest.getFilters();


        //如果过滤条件不为空
        if (!CollectionUtils.isEmpty(filters)){

            filters.entrySet().forEach(entry->{
                String filterKey = entry.getKey();
                String filterValue = entry.getValue();

                if ("分类".equals(filterKey)){
                    queryBuilder.filter(QueryBuilders.termQuery(CATEGORY_FIELD_NAME,filterValue));
                }else if("品牌".equals(filterKey)){
                    queryBuilder.filter(QueryBuilders.termQuery(BRAND_FIELD_NAME,filterValue));
                }else {
                    //其他过滤参数，要使用nested查询
                    BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                    //filterKey对应的属性为 specs.name
                    nestedBoolQuery.must(QueryBuilders.matchQuery(SPEC_NAME_FIELD_NAME,filterKey));
                    //filtersValue对应的属性为 specs.value
                    nestedBoolQuery.must(QueryBuilders.matchQuery(SPEC_VALUE_FIELD_NAME,filterValue));
                    //添加一个nested查询，nested内部为布尔查询，
                    queryBuilder.filter(QueryBuilders.nestedQuery(SPEC_FIELD_PATH,nestedBoolQuery, ScoreMode.None));
                }
            });
        }
        return queryBuilder;
    }


    private Goods buildGoods(SpuDTO spu) {
        // 1.自动补全的提示字段 ：分别包含，品牌，分类，品牌+分类，分类+品牌，商品名称，品牌+商品名称
        List<String> suggestion = new ArrayList<>(
                Arrays.asList(StringUtils.split(spu.getCategoryName(), "/")));
        suggestion.add(spu.getName());
        suggestion.add(spu.getBrandName());

        // 2.sku的价格集合
        // 2.1.查询sku集合
        List<SkuDTO> skuList = spu.getSkus();
        if (CollectionUtils.isEmpty(skuList)) {
            // 没有sku，我们去查询
            skuList = itemClient.querySkuBySpuId(spu.getId());
        }
        // 2.2.获取价格集合
        Set<Long> prices = skuList.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        // 3.商品销量
        long sold = skuList.stream().mapToLong(SkuDTO::getSold).sum();

        //substringBefore 从分隔符第一次出现的位置向前截取
        //substringBeforeLast 从分隔符最后一次出现的位置向前截取：
        //substringAfter 从分隔符第一次出现的位置向后截取
        //substringAfterLast 从分隔符最后一次出现的位置向后截取
        // 4.sku的某个图片
        String image = StringUtils.substringBefore(skuList.get(0).getImages(), ",");

        // 5.规格参数
        List<Map<String, Object>> specs = new ArrayList<>();
        // 5.1.查询规格参数name和value键值对，只查询参与搜索的
        List<SpecParamDTO> params = itemClient.querySpecsValues(spu.getId(), true);
        log.info("search_service:{}",params);
        // 5.2.封装
        for (SpecParamDTO param : params) {
            Map<String, Object> map = new HashMap<>(2);
            //name，规格参数的名称，value，规格参数的值
            map.put("name", param.getName());
            //保存值应该给可以进行区间处理的值，划分区间
            map.put("value", chooseSegment(param));
            specs.add(map);
        }

        // 创建Goods对象，并封装数据
        Goods goods = new Goods();
        goods.setUpdateTime(new Date());
        // 自动补全的提示字段
        goods.setSuggestion(suggestion);
        // 规格参数
        goods.setSpecs(specs);
        // 商品销量
        goods.setSold(sold);
        // 商品标题  将数组或集合以某拼接符（空格）拼接到一起形成新的字符串
        goods.setTitle(spu.getTitle() + StringUtils.join(suggestion, " "));
        // sku的价格集合
        goods.setPrices(prices);
        // sku的某个图片
        goods.setImage(image);
        goods.setCategoryId(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setId(spu.getId());
        return goods;
    }

    private Object chooseSegment(SpecParamDTO p) {
        Object value = p.getValue();
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        if (!p.getNumeric() || StringUtils.isBlank(p.getSegments()) || value instanceof Collection) {
            return value;
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }
}
